using System;
using System.Collections.Generic;
using System.Linq;
using Kiwi.Container.Activation;
using Kiwi.Container.DependencyInjection;

namespace Kiwi.Container.Internal
{
    public class ComponentResolver : IComponentResolver, IInternalComponentResolver
    {
        private readonly IInternalContainer _container;
        private readonly Dictionary<IComponentRegistration, object> _resolvedComponents;
        private HashSet<IComponentRegistration> _activeCreators;
        private IInternalComponentResolver _parent;

        public ComponentResolver(IInternalContainer container)
            : this(container, new Dictionary<IComponentRegistration, object>(), null)
        {
        }

        private ComponentResolver(IInternalContainer container,
                                  Dictionary<IComponentRegistration, object> resolvedComponents,
                                  HashSet<IComponentRegistration> activeCreators)
        {
            _container = container;
            _resolvedComponents = resolvedComponents;
            _activeCreators = activeCreators;
        }

        private IInternalComponentResolver Parent
        {
            get
            {
                if (_parent == null)
                {
                    if (_container.InternalParent != null)
                    {
                        _parent = new ComponentResolver(_container.InternalParent, _resolvedComponents,
                                                        _activeCreators);
                    }
                    else
                    {
                        _parent = NullInternalComponentResolver.Default;
                    }
                }
                return _parent;
            }
        }

        #region IComponentResolver Members

        public bool HasComponent(Type type)
        {
            return _container.HasComponent(type);
        }

        public object TryResolve(Type type, Func<IComponentRegistration, bool> filter)
        {
            IComponentRegistration c;
            return TryResolveInternal(type, filter, out c);
        }

        public object Resolve(Type type, Func<IComponentRegistration, bool> filter)
        {
            var component = TryResolve(type, filter);
            if (component == null)
            {
                Throw.ComponentNotFound(type);
            }
            return component;
        }

        public IEnumerable<object> ResolveAll(Type type, Func<IComponentRegistration, bool> filter)
        {
            using (CreateResolveContext())
            {
                var result = new List<object>();
                while (true)
                {
                    IComponentRegistration registration;
                    var component = TryResolveInternal(type, filter, out registration);
                    if (component == null)
                    {
                        break;
                    }
                    result.Add(component);
                    _activeCreators.Add(registration);
                }
                return result;
            }
        }

        public void Inject(object instance, IEnumerateDependentProperties dependentProperties)
        {
            if (dependentProperties is ISkipEnumerateDependentProperties)
            {
                return;
            }
            foreach (
                var dependency in dependentProperties.EnumerateDependentProperties(instance, _container.HasComponent))
            {
                var component = TryResolve(dependency.Type, null);
                if (component != null)
                {
                    dependency.Update(component);
                }
            }
        }

        public object Construct(Type type, IEnumerateDependentProperties dependentProperties)
        {
            var component = BestFitConstructorComponentActivator.ActivateComponent(type, this);
            Inject(component, dependentProperties);
            return component;
        }

        #endregion

        #region IInternalComponentResolver Members

        public object GetCachedComponent(IComponentRegistration registration)
        {
            using (_container.CreateLock())
            {
                object component;
                if (_container.TryGetComponent(registration, out component))
                {
                    return component;
                }
            }

            return Parent.GetCachedComponent(registration);
        }

        #endregion

        private object TryResolveInternal(Type type, Func<IComponentRegistration, bool> filter,
                                          out IComponentRegistration registration)
        {
            using (CreateResolveContext())
            {
                filter = filter ?? (c => true);

                registration = GetComponentCreator(type, filter);
                if (registration == null)
                {
                    return Parent.TryResolve(type, filter);
                }

                _activeCreators.Add(registration);

                var component = Resolve(registration, filter);

                _activeCreators.Remove(registration);
                return component;
            }
        }

        private ResolveContext CreateResolveContext()
        {
            return new ResolveContext(this);
        }

        private void ThrowIfCircularDependencies(IComponentRegistration registration,
                                                 Func<IComponentRegistration, bool> filter)
        {
            using (_container.CreateLock())
            {
                var circularCreator = _container.CreatorLookup[registration.ComponentType.Type]
                    .Where(c => c.ComponentScope.IsCompatibleContainer(_container.Scope))
                    .Where(filter)
                    .LastOrDefault();
                if (circularCreator != null)
                {
                    Throw.CircularComponentActivation(circularCreator, _activeCreators);
                }
            }
        }

        private object Resolve(IComponentRegistration registration, Func<IComponentRegistration, bool> filter)
        {
            try
            {
                var component = GetCachedComponent(registration);
                if (component != null)
                {
                    return component;
                }

                if (_resolvedComponents.TryGetValue(registration, out component))
                {
                    return component;
                }

                component = registration.Activator.ActivateComponent(this);
                _resolvedComponents.Add(registration, component);
                Inject(component, registration.InjectProperties);

                using (_container.CreateLock())
                {
                    _container.AddComponent(registration, component);
                }

                return component;
            }
            catch (ComponentNotFoundException)
            {
                ThrowIfCircularDependencies(_activeCreators.DefaultIfEmpty(registration).First(),
                                            filter);
                throw;
            }
        }

        private IComponentRegistration GetComponentCreator(Type type, Func<IComponentRegistration, bool> filter)
        {
            using (_container.CreateLock())
            {
                return _container.CreatorLookup[type]
                    .Where(c => c.ComponentScope.IsCompatibleContainer(_container.Scope))
                    .Where(filter)
                    .Except(_activeCreators).LastOrDefault();
            }
        }

        #region Nested type: ResolveContext

        private class ResolveContext : IDisposable
        {
            private readonly bool _isRecursionRoot;
            private readonly ComponentResolver _owner;

            public ResolveContext(ComponentResolver owner)
            {
                _owner = owner;
                _isRecursionRoot = _owner._activeCreators == null;
                if (_isRecursionRoot)
                {
                    _owner._activeCreators = new HashSet<IComponentRegistration>();
                }
            }

            #region IDisposable Members

            public void Dispose()
            {
                if (_isRecursionRoot)
                {
                    _owner._activeCreators = null;
                }
            }

            #endregion
        }

        #endregion
    }
}